#pragma once
#include <concepts>
#include <coroutine>
#include <type_traits>
#include <utility>

namespace wb
{
namespace concepts
{
// clang-format off
template <typename T>
concept awaiter = requires(T t, std::coroutine_handle<> h)
{
    { t.await_ready() } ->std::same_as<bool>;
    { t.await_resume() };
    std::same_as<decltype(t.await_suspend(h)), void> ||
    std::same_as<decltype(t.await_suspend(h)), bool> ||
    std::same_as<decltype(t.await_suspend(h)), std::coroutine_handle<>>;
};

template <typename T>
concept awaitable = requires(T t)
{
    { t.operator co_await() } -> awaiter;
};

template <typename T>
concept awaiter_void = requires(T t, std::coroutine_handle<> h)
{
    { t.await_ready() } ->std::same_as<bool>;
    { t.await_resume() } -> std::same_as<void>;
    std::same_as<decltype(t.await_suspend(h)), void> ||
    std::same_as<decltype(t.await_suspend(h)), bool> ||
    std::same_as<decltype(t.await_suspend(h)), std::coroutine_handle<>>;
};

template <typename T>
concept awaitable_void = requires(T t)
{
    { t.operator co_await() } -> awaiter_void;
};

template <awaitable T, typename = void>
struct awaitable_traits
{
};

template <awaitable T>
static auto get_awaiter(T&& value)
{
    return std::forward<T>(value).operator co_await();
}

template <awaitable T>
struct awaitable_traits<T>
{
    using awaiter_type = decltype(get_awaiter(std::declval<T>()));
    using awaiter_return_type = decltype(std::declval<awaiter_type>().await_resume());
};

// clang-format on
}  // namespace concepts
}  // namespace wb